package com.simple.core.log;

import cn.hutool.core.date.SystemClock;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.simple.annotation.log.SysLog;
import com.simple.core.config.mdc.MDCConstant;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 日志记录AOP
 *
 * @author Clarence
 */
@Slf4j
@Aspect
@Order(-1)
@Component
public class SysLogAspect {
    private static final TemplateParserContext TEMPLATE_PARSER_CONTEXT = new TemplateParserContext("{{", "}}");
    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
    @Autowired
    private SysLogService sysLogService;

    @Around("@annotation(com.simple.annotation.log.SysLog)")
    public Object doMethodAround(ProceedingJoinPoint joinPoint) throws Throwable {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        String method = StrFormatter.format("{}::{}", className, methodName);

        String classDesc = initClassDesc(joinPoint);
        String methodDesc = parseMethodExpress(joinPoint);

        log.debug("{} - {} - {} 方法开始执行，参数：{}", method, classDesc, methodDesc, JSONUtil.toJsonStr(filterArgs(joinPoint.getArgs())));
        long beginTime = SystemClock.now();
        try {
            Object result = joinPoint.proceed();
            Long time = SystemClock.now() - beginTime;
            methodDesc = parseMethodExpress(joinPoint);
            log.debug("{} - {} - {} 方法执行成功，耗时：{}ms，返回值：{}", method, classDesc, methodDesc, time, JSONUtil.toJsonStr(result));
            addLog(method, classDesc, methodDesc, time.intValue(), true, null);
            return result;
        } catch (Exception e) {
            Long time = SystemClock.now() - beginTime;
            log.error("{} - {} - {} 方法执行异常，耗时：{}ms，报错：{}", method, classDesc, methodDesc, time, JSONUtil.toJsonStr(e.getMessage()));
            addLog(method, classDesc, methodDesc, time.intValue(), false, StrUtil.subPre(e.getMessage(), 2000));
            throw e;
        }
    }

    private void addLog(String method, String classDesc, String methodDesc, Integer executeTime, Boolean isSuccess, String errorMsg) {
        String requestId = MDC.get(MDCConstant.REQUEST_ID_KEY);
        SysLogs sysLogs = new SysLogs(requestId, method, classDesc, methodDesc, isSuccess, errorMsg, executeTime);
        sysLogService.save(sysLogs);
    }

    private String initClassDesc(JoinPoint joinPoint) {
        Api apiAnt = joinPoint.getTarget().getClass().getAnnotation(Api.class);
        String classDesc = "";
        if (Objects.nonNull(apiAnt)) {
            String tags = Arrays.toString(apiAnt.tags());
            classDesc = StrUtil.sub(tags, 1, tags.length() - 1);
        }
        return classDesc;
    }

    /**
     * 解析SpEl表达式
     *
     * @param joinPoint
     * @return
     */
    private String parseMethodExpress(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = signature.getParameterNames();
        Method targetMethod = signature.getMethod();
        SysLog sysLog = targetMethod.getAnnotation(SysLog.class);
        EvaluationContext evaluationContext = new StandardEvaluationContext();
        for (int i = 0; i < args.length; i++) {
            evaluationContext.setVariable(parameterNames[i], args[i]);
        }
        String logContent = EXPRESSION_PARSER.parseExpression(sysLog.expression(), TEMPLATE_PARSER_CONTEXT).getValue(evaluationContext, String.class);
        return logContent;
    }

    private List<Object> filterArgs(Object[] objects) {
        return Arrays.stream(objects).filter(obj -> !(obj instanceof MultipartFile)
                && !(obj instanceof HttpServletResponse)
                && !(obj instanceof HttpServletRequest)).collect(Collectors.toList());
    }

}
